'use strict';

import chai from 'chai';
import 'chai/register-should';
import sinon from 'sinon';
import { V8 } from '../src/lib/blockchainexplorers/v8';
import { BitcoreLib } from 'crypto-wallet-core';
import { Readable } from 'stream';
import { Common } from '../src/lib/common';

const should = chai.should();
const { Defaults } = Common;
const V8UTXOS = [
  { '_id': '5c1d4bc47adced963b3cddb9', 'chain': 'BCH', 'network': 'testnet', 'coinbase': false, 'mintIndex': 0, 'spentTxid': '', 'mintTxid': '6e34d9b83631cd55ee09d907061332ba3c17246e3c1255543fb7a35e58c52e42', 'mintHeight': 12, 'spentHeight': -2, 'address': 'qrua7vsdmks4522wwv8rtamfph7g8s8vpq6a0g3veh', 'script': '76a914f9df320ddda15a294e730e35f7690dfc83c0ec0888ac', 'value': 1000000, 'confirmations': -1 },
  { '_id': '5c1e33e17adced963b776bcf', 'chain': 'BCH', 'network': 'testnet', 'coinbase': false, 'mintIndex': 0, 'spentTxid': '', 'mintTxid': 'fb1340bae2431f71c5f14d0c5893cbfb09042dcb9602b858ccec43e0e1e2f1a1', 'mintHeight': 15, 'spentHeight': -2, 'address': 'qrua7vsdmks4522wwv8rtamfph7g8s8vpq6a0g3veh', 'script': '76a914f9df320ddda15a294e730e35f7690dfc83c0ec0888ac', 'value': 2000000, 'confirmations': -1 },
  { '_id': '5c21088f7adced963b33eea2', 'chain': 'BCH', 'network': 'testnet', 'coinbase': false, 'mintIndex': 0, 'spentTxid': '', 'mintTxid': '42eeb1d139521fa5206685ffec5df3b302cf85561201178680a0efe6bd23d449', 'mintHeight': -1, 'spentHeight': -2, 'address': 'qrua7vsdmks4522wwv8rtamfph7g8s8vpq6a0g3veh', 'script': '76a914f9df320ddda15a294e730e35f7690dfc83c0ec0888ac', 'value': 2000000, 'confirmations': -1 }
];

const V8UTXOS2 = [ 
  { _id: '5cb4f9d612025b0a3931b13c', chain: 'BTC', network: 'mainnet', coinbase: false, mintIndex: 0, spentTxid: '', mintTxid: '623f72b089da60a179d7b85b50ed655e8580747ee06f2f77369cacfb99de11a0', mintHeight: 571792, spentHeight: -2, address: '38o49rd64PFDmvUV7928K1a5SRnoVgJSFW', script: 'a9144ded3cc47fcf6883a78c29134f90b0c1b0c368c887', value: 109810934, confirmations: 126 },
  { _id: '5cb503e612025b0a393d2ea9', chain: 'BTC', network: 'mainnet', coinbase: false, mintIndex: 0, spentTxid: '', mintTxid: '06ab9db9100409132a4c1367b87f16983938007dbae7b96a0746a64a7755e3e6', mintHeight: 571797, spentHeight: -2, address: '36pUaXzGouNdCqUDRWRXX9NJYungJEWJC2', script: 'a9143841ca886a1c4276966a77a15d0d1c4fe1e841bd87', value: 350000000, confirmations: 121 }
];

const t = new Date().toISOString();
const external = '11234';
const txsStr = '{"id":"63863c730c84018998d248f1","txid":"e7d7362ff5e917c751ca54edc5022930ecc28f521b74851028a723c02ce2ad37","fee":225000,"size":225,"category":"receive","satoshis":1025000000,"height":-1,"address":"mi4rc4nKmWHHeNY4693cHofQY6gRLZW48P","outputIndex":1,"blockTime":"2022-11-29T15:59:11.000Z"}\n{"id":"63863c740c84018998d24f51","txid":"17966f1c514679eb55ccdacaa66a8186e433a06372b06c689e5514ff9b4442cb","fee":372000,"size":372,"category":"receive","satoshis":1055000000,"height":5589,"address":"mgAFBzA2M1yqFuXHvkUBUFMGM7CTzoEZgN","outputIndex":1,"blockTime":"2022-11-29T16:00:24.000Z"}\n{"id":"63863c750c84018998d255b7","txid":"52e6aa1d231a9c79f08f69d48fc91c28e36f2100f4bcb2aae270d3656d80bd96","fee":1232128,"size":1219,"category":"receive","satoshis":10000000000,"height":5689,"address":"n2bmPmt2z5MJ8SA1QR2uViKofBJE9wg2Wf","outputIndex":0,"blockTime":"2022-11-29T16:57:01.000Z"}\n';

describe('V8', () => {
  const wallet = {
    beAuthPrivateKey2: new BitcoreLib.PrivateKey()
  };

  describe('#listTransactions', () => {
    it('should handle partial json results', (done) => {
      class PartialJson {
        listTransactions(opts) {
          class MyReadable extends Readable {
            constructor(options?) {
              super(options);
              this.push(txsStr.substring(0, 10));
              this.push(txsStr.substring(10));
              this.push(null);
            }
          };

          return new MyReadable();
        };
      };

      const be = new V8({
        chain: 'btc',
        network: 'livenet',
        url: 'http://dummy/',
        apiPrefix: 'dummyPath',
        userAgent: 'testAgent',
        client: PartialJson as any,
      });

      be.getTransactions(wallet as any, 0, (err, txs) => {
        should.not.exist(err);
        should.exist(txs);
        txs.length.should.equal(3);
        return done();
      });
    });

    it('should handle partial jsonline results', (done) => {
      class PartialJsonL {
        listTransactions(opts) {
          class MyReadable extends Readable {
            constructor(options?) {
              super(options);
              const txStr = '{ "id": 1, "txid": "txid1", "confirmations": 1, "blockTime": "' +
                t + '", "size": 226, "category": "send", "height": 123, "toAddress": "' +
                external + '", "satoshis": 0.5e8 } \n { "id": 2, "txid": "txid2", "confirmations": 1, "category": "send", "blockTime": "' +
                t + '", "satoshis": 0.3e8, "height": 123, "toAddress": "' + external + '"}';
              this.push(txStr.substring(0, 10));
              this.push(txStr.substring(10));
              this.push(null);
            }
          };

          return new MyReadable();
        };
      };

      const be = new V8({
        chain: 'btc',
        network: 'livenet',
        url: 'http://dummy/',
        apiPrefix: 'dummyPath',
        userAgent: 'testAgent',
        client: PartialJsonL as any,
      });

      be.getTransactions(wallet as any, 0, (err, txs) => {
        should.not.exist(err);
        should.exist(txs);
        txs.length.should.equal(2);
        return done();
      });
    });

  });

  describe('#getAddressUtxos', () => {
    it('should get uxtos', (done) => {
      class PartialJson {
        getAddressTxos(opts) {
          return new Promise(function (resolve) {
            resolve(V8UTXOS);
          });
        };
      };

      const be = new V8({
        chain: 'bch',
        network: 'livenet',
        url: 'http://dummy/',
        apiPrefix: 'dummyPath',
        userAgent: 'testAgent',
        client: PartialJson as any,
      });

      be.getAddressUtxos('1EU9VhWRN7aW38pGk7qj3c2EDcUGDZKESt', 15, (err, utxos) => {
        should.not.exist(err);
        should.exist(utxos);
        const x = utxos[2];
        x.confirmations.should.equal(0);
        x.address.should.equal('qrua7vsdmks4522wwv8rtamfph7g8s8vpq6a0g3veh');
        x.satoshis.should.equal(2000000);
        x.amount.should.equal(x.satoshis/1e8);
        x.scriptPubKey.should.equal('76a914f9df320ddda15a294e730e35f7690dfc83c0ec0888ac');
        x.txid.should.equal('42eeb1d139521fa5206685ffec5df3b302cf85561201178680a0efe6bd23d449');
        x.vout.should.equal(0);

        utxos[1].confirmations.should.equal(1);
        utxos[0].confirmations.should.equal(4);

        return done();
      });
    });

    it('should get uxtos 2', (done) => {
      class PartialJson {
        getAddressTxos(opts) {
          return Promise.resolve(V8UTXOS2);
        };
      };

      const be = new V8({
        chain: 'bch',
        network: 'livenet',
        url: 'http://dummy/',
        apiPrefix: 'dummyPath',
        userAgent: 'testAgent',
        client: PartialJson as any,
      });

      be.getAddressUtxos('36pUaXzGouNdCqUDRWRXX9NJYungJEWJC2', 571920, (err, utxos) => {
        should.not.exist(err);
        should.exist(utxos);
        const x = utxos[1];
        x.confirmations.should.equal(124);
        x.satoshis.should.equal(350000000);
        x.amount.should.equal(3.5);
        return done();
      });
    });

  });

  describe('#estimateFee', () => {
    it('should estimate fee', (done) => {
      const fakeRequest = {
        get: sinon.stub().resolves('{"feerate":0.00017349,"blocks":5}'),
      };

      const be = new V8({
        chain: 'bch',
        network: 'livenet',
        url: 'http://dummy/',
        apiPrefix: 'dummyPath',
        userAgent: 'testAgent',
        request: fakeRequest,
      });

      be.estimateFee([5], (err, levels) => {
        should.not.exist(err);
        should.exist(levels);
        // should ignore non-matching results
        levels.should.deep.equal({ '5': 0.00017349 });
        return done();
      });
    });

    it('should ignore non-matching results from estimate fee', (done) => {
      const fakeRequest = {
        get: sinon.stub().resolves('{"feerate":0.00017349,"blocks":4}'),
      };

      const be = new V8({
        chain: 'bch',
        network: 'livenet',
        url: 'http://dummy/',
        apiPrefix: 'dummyPath',
        userAgent: 'testAgent',
        request: fakeRequest,
      });

      be.estimateFee([1, 2, 3, 4, 5], (err, levels) => {
        should.not.exist(err);
        should.exist(levels);
        // should ignore non-matching results
        levels.should.deep.equal({ '4': 0.00017349 });
        return done();
      });
    });

    it('should use results from estimate fee is blocks is not present', (done) => {
      const fakeRequest = {
        get: sinon.stub().resolves('{"feerate":0.00017349}'),
      };

      const be = new V8({
        chain: 'bch',
        network: 'livenet',
        url: 'http://dummy/',
        apiPrefix: 'dummyPath',
        userAgent: 'testAgent',
        request: fakeRequest,
      });

      be.estimateFee([1, 2, 3, 4, 5], (err, levels) => {
        should.not.exist(err);
        should.exist(levels);
        levels.should.deep.equal({
          '1': 0.00017349,
          '2': 0.00017349,
          '3': 0.00017349,
          '4': 0.00017349,
          '5': 0.00017349
        });
        return done();
      });
    });

  });


  describe('#broadcast', () => {
    it('should broadcast a TX', (done) => {
      class BroadcastOk {
        broadcast(payload) {
          return Promise.resolve({ txid: 'txid' });
        };
      };

      const be = new V8({
        chain: 'bch',
        network: 'livenet',
        url: 'http://dummy/',
        apiPrefix: 'dummyPath',
        userAgent: 'testAgent',
        client: BroadcastOk as any,
      });

      be.broadcast('xxx', (err, txid) => {
        should.not.exist(err);
        txid.should.equal('txid');
        done();
      });
    });

    it('should fail to broadcast an invalid TX', (done) => {
      class BroadcastInvalid {
        broadcast(payload) {
          return Promise.resolve('invalid');
        };
      };

      const be = new V8({
        chain: 'bch',
        network: 'livenet',
        url: 'http://dummy/',
        apiPrefix: 'dummyPath',
        userAgent: 'testAgent',
        client: BroadcastInvalid as any,
      });

      be.broadcast('xxx', (err, txid) => {
        should.not.exist(txid);
        err.toString().should.contain('Error');
        done();
      });
    });


    it('should retry to broadcast is socket hang up', (done) => {
      const oldd = Defaults.BROADCAST_RETRY_TIME;
      Defaults.BROADCAST_RETRY_TIME = 5;
      let x = 0;
      class BroadcastInvalid {
        broadcast(payload) {
          return new Promise((resolve, reject) => {
            if (x++ < 2) {
              reject('socket err or');
            } else {
              resolve({ txid: 'txid' });
            }
          });
        };
      };

      const be = new V8({
        chain: 'bch',
        network: 'livenet',
        url: 'http://dummy/',
        apiPrefix: 'dummyPath',
        userAgent: 'testAgent',
        client: BroadcastInvalid as any,
      });

      be.broadcast('xxx', (err, txid) => {
        should.not.exist(err);
        txid.should.equal('txid');
        Defaults.BROADCAST_RETRY_TIME = oldd;
        done();
      });
    });
  });
});
